package com.remote;

import android.app.Service;
import android.content.Intent;
import android.os.*;

import java.net.*;
import android.net.wifi.*;

import java.io.IOException;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import java.util.ArrayList;
import android.util.Log;


import android.content.Context;

public class Gateway extends Service{
    Worker worker;
    Receiver receiver;
    
    WifiManager wifiManager = null;
    
    private int RecvPort = 0;
    private int SendPort = 0;
    private InetAddress SendAddress = null;
    
    private int tmpSendPort = 0;
    private InetAddress tmpSendAddress = null;
    
    private final String TAG = this.getClass().getName();
    
    private DatagramSocket SenderSocket = null;
    private DatagramSocket ReceiverSocket = null;
    
    LinkedBlockingQueue<String[]> msgQueue = new LinkedBlockingQueue<String[]>();
    
    // TODO - prerobit na jedneho clienta --- a osetrit ak sa registruje novy a stary neni odregistrovany -> odoslanie unregister msg aktivite
    Messenger mClient = null;
    
    class IncomingHandler extends Handler{
        public void handleMessage(Message msg){
            Log.v("handler","received msg.what: "+msg.what+" - "+msg.arg1);
            if(mClient != msg.replyTo) mClient = msg.replyTo;
            switch(msg.what){
                case Params.MSG_START_LISTENING:
                    RecvPort = msg.arg1;
                    worker = new Worker("Worker");
                    receiver = new Receiver("Receiver");
                    break;
                case Params.MSG_REGISTER_ACTIVITY:
                    Log.i(TAG,"Registering Activity");
                    mClient = msg.replyTo;
                    sendMsg(Params.MSG_REGISTER_ACTIVITY);
                    break;
                case Params.MSG_INITIALIZE:
                    Log.i(TAG,"Initializing Traffic");
                    Bundle bData = msg.peekData();
                    if(bData == null) break;        // ignore if no server info sent -- should never happen
                    try{
                        tmpSendAddress = InetAddress.getByName(bData.getString("address"));
                        tmpSendPort = bData.getInt("port");
                        
                        sendPacket("login|"+String.valueOf(RecvPort),tmpSendAddress,tmpSendPort);
                    }catch(UnknownHostException e){
                        Log.e(TAG,e.toString());
                    }
                    break;
                case Params.MSG_SEARCH_SERVER:
                    Log.i(TAG,"Searching for server");
                    searchServer(msg.arg1);
                    break;
                case Params.MSG_SOUND_SET:
                    Log.i(TAG,"Sending set sound "+msg.arg1);
                    sendPacket("action|sound|setmute|"+msg.arg1);
                    break;
                case Params.MSG_SOUND_GET:
                    Log.i(TAG,"Sending get sound");
                    sendPacket("action|sound|getmute");
                    break;
                case Params.SEND_PACKET:
					Log.i(TAG,"Sending packet");
					sendPacket(msg.peekData().getString("data"));
					break;
                default:
                    Log.d(TAG,"Received unhandled msg");
            }
        }
    }
    
    final Messenger msgReceiver = new Messenger(new IncomingHandler());
    
    @Override
    public void onCreate(){
        Log.i(TAG,"Creating Gateway");
    }
    
    @Override
    public void onDestroy(){
        Log.i(TAG,"Destroying Gateway");
    }

    @Override
    public IBinder onBind(Intent intent){
        Log.v(TAG,"RUNNING ONBIND");
        return msgReceiver.getBinder();
    }
    
    //! thread class parsing incoming data and doing work
    class Worker extends RemoteThread{
        public Worker(String name){
            thread = new Thread(this,name);
            thread.start();
        }
        
        @Override
        public void run(){
            Log.i(thread.getName(),"Running");
            
            try{
                SenderSocket = new DatagramSocket();
                SenderSocket.setSoTimeout(Params.SOCKET_TIMEOUT);     // waiting N seconds to receive packet
            }catch(SocketException e1){
                Log.e(thread.getName(),"port didnt open : "+e1.toString());
                return;         // end if socket wasnt created
            }
            
            while(!this.getEnd()){
                try{
                    
                    String[] input = msgQueue.poll(Params.MSGQUEUE_TIMEOUT,TimeUnit.SECONDS);
                    if(input==null){
                        Log.i(thread.getName(),"no msg polled");
                        continue;       // if no msg polled
                    }
                    
                    for(int i=0;i<input.length;i++) Log.i(thread.getName(),input[i]);
                    
                    parseData(input);
                    
                }catch(InterruptedException e){
                    Log.e(thread.getName(),e.toString());
                }
            }
            
            Log.i(thread.getName(),"Finished");
        }
        
        private void parseData(String[] data){
            if(data[0].compareTo("search")==0){
                sendMsg(Params.MSG_SERVER_RESPONSE,Utils.getServerBundle(Utils.delFirstElement(data)));
            }else if(data[0].compareTo("initialized")==0){
                sendMsg(Params.MSG_INITIALIZED);
                SendAddress=tmpSendAddress;
                SendPort=tmpSendPort;
            }else if(data[0].compareTo("sound")==0){
                sendMsg(Params.MSG_SOUND_SET,Integer.parseInt(data[1]));
            }
        }
    }

    //! thread class receiving packets and parsing them
    class Receiver extends RemoteThread{
        DatagramPacket incPacket;
        String incPacketData;
        String[] deserializedData;
        String control;
        
        int ping = 0;
        
        public Receiver(String name){
            incPacket = new DatagramPacket(new byte[1024],1024);
            thread = new Thread(this,name);
            thread.start();
        }
        
        @Override
        public void run(){
            Log.i(thread.getName(),"Running");
            
            try{
                ReceiverSocket = new DatagramSocket(RecvPort);
                ReceiverSocket.setSoTimeout(Params.SOCKET_TIMEOUT);     // waiting N seconds to receive packet
            }catch(SocketException e1){
                Log.e(thread.getName(),"port didnt open : "+e1.toString());
                return;         // end if socket wasnt created
            }
            
            Log.i(thread.getName(),"Socket created");
            
            while(!this.getEnd()){
                try{
                    ReceiverSocket.receive(incPacket);
                    incPacketData = new String(incPacket.getData(),0,incPacket.getLength());
                    Log.i(thread.getName(),"ReceivedPacket - "+incPacketData);
                    Log.i(thread.getName(),"From: "+incPacket.getAddress().toString()+" - "+incPacket.getPort());
                    
                    deserializedData = incPacketData.split("\\|",2);
                    
                    if(Utils.check_data(deserializedData[0],deserializedData[1])){
                        deserializedData = deserializedData[1].split("\\|");
                    }else{
                        Log.e(thread.getName(),"MD5 check failed");
                        continue;       // ignore packet if bad data
                    }
                    
                    
                    
                    msgQueue.put(deserializedData);
                    Log.i(thread.getName(),"Inserted data into queue");
                    
                    ping = 0;       // reset ping counter on received packet
                }catch(IOException e){
                    Log.v(thread.getName(),"Receiving packet timeouted #"+ping);
                    // TODO Timeout management
/*
                    if(ping>5){
                        this.endThread();
                        worker.endThread();
                    }else ping++;
*/
                }catch(InterruptedException e2){
                    Log.e(thread.getName(),e2.toString());
                }
            }
            
            Log.i(thread.getName(),"Finished");
        }        
    }
    
    //! finds remote server in local network
    private void searchServer(int searchPort){
        try{
            SenderSocket = new DatagramSocket();
        }catch(SocketException e){
            Log.e(TAG,e.toString());
            return;     // end if socket wasnt created
        }
        
        String address = Utils.getLocalIpAddress();
        
        // EMULATOR HACK
/*
        address = "192.168.1.2";
*/
/*
		address = "192.168.100.113";
*/
        
        String network = address.substring(0,address.lastIndexOf('.')+1);
        String data = Utils.getPacketData("search|"+String.valueOf(this.RecvPort));
        
        Log.v(TAG,network);
        
        byte[] buffer = data.getBytes();
        
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
        packet.setPort(searchPort);
        
        for(int i=1;i<255;i++){
            try{
                packet.setAddress(InetAddress.getByName(network+String.valueOf(i)));
                SenderSocket.send(packet);
            }catch(UnknownHostException e1){
                Log.e(TAG,e1.toString());
            }catch(IOException e2){
                Log.e(TAG,e2.toString());
            }
        }
        Log.i(TAG,"Packets sent");
    }

    //! initializing sending port and address
    private void initTraffic(InetAddress SendAddress,int SendPort){
        Log.i(TAG,"initializing sendport and sendaddress");
        this.SendPort = SendPort;
        this.SendAddress = SendAddress;
    }
    
    private void setWifi(){
        if(wifiManager == null) wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
        if(!wifiManager.isWifiEnabled()){
            Log.i(TAG,"Wifi is not enabled");
            wifiManager.setWifiEnabled(true);
        }
    }
    
    //! returns true on sent , false on error
    private boolean sendPacket(String data){
        try{
            data = Utils.getPacketData(data);
            byte[] buffer = data.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length,SendAddress,SendPort);
            SenderSocket.send(packet);
            Log.i(TAG,"Packet sent to "+SendAddress.getHostAddress()+" on port "+SendPort+" - "+data);
        }catch(IOException e){
            Log.e(TAG,data+"|"+e.toString());
            return false;
        }
        return true;
    }
    
    //! returns true on sent , false on error
    private boolean sendPacket(String data,InetAddress address,int port){
        try{
            data = Utils.getPacketData(data);
            byte[] buffer = data.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length,address,port);
            SenderSocket.send(packet);
        }catch(IOException e){
            Log.e(TAG,data+"|"+e.toString());
            return false;
        }
        return true;
    }
    
    protected boolean sendMsg(int what){
        try{
            Message msg = Message.obtain(null,what);
            msg.replyTo = this.msgReceiver;
            mClient.send(msg);
        }catch(RemoteException e){
            Log.e(TAG,"Service not running");
            return false;
        }
        return true;
    }
    
    protected boolean sendMsg(int what,int arg1){
        try{
            Message msg = Message.obtain(null,what);
            msg.replyTo = this.msgReceiver;
            msg.arg1=arg1;
            mClient.send(msg);
        }catch(RemoteException e){
            Log.e(TAG,"Service not running");
            return false;
        }
        return true;
    }
    
    protected boolean sendMsg(int what,Bundle bData){
        try{
            Message msg = Message.obtain(null,what);
            msg.replyTo = this.msgReceiver;
            if(bData!=null) msg.setData(bData);
            mClient.send(msg);
        }catch(RemoteException e){
            Log.e(TAG,"Service not running");
            return false;
        }
        return true;
    }
}
